import pytest
import os
import threading
import subprocess
import argparse
import time
import zipfile
from pathlib import Path
import yagmail
import logging
from config.constant import (
    REPORTS_DIR, REPORTS_TEMP_DIR, LOGS_DIR, ALLURE_REPORT_DIR, TESTS_DIR, PROJECT_ROOT
)

# 配置日志
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(message)s")
logger = logging.getLogger(__name__)

# 创建线程锁，确保多线程操作安全
result_lock = threading.Lock()


def send_mail(to_mail, subject, contents, attachments):
    """
    发送邮件方法
    :param to_mail: 收件人邮箱
    :param subject: 邮件主题
    :param contents: 邮件内容
    :param attachments: 附件路径列表
    """
    try:
        mail = yagmail.SMTP(
            user='2101241257@qq.com',
            password=os.getenv("AUTHORIZATION_CODE"),
            host='smtp.qq.com'
        )
        mail.send(
            to=to_mail,
            subject=subject,
            contents=contents,
            attachments=attachments
        )
        logger.info(f"邮件发送成功: {to_mail}")
        return True
    except Exception as e:
        logger.error(f"邮件发送失败: {e}")
        return False


def zip_logs_reports(output_zip: str, *paths: str):
    """
    将任意数量的文件/目录打包成 zip
    :param output_zip: 输出压缩包路径
    :param paths: 可变参数，要打包的文件或目录
    :return: 压缩包绝对路径
    """
    output_zip = Path(output_zip).resolve()
    with zipfile.ZipFile(output_zip, 'w', compression=zipfile.ZIP_DEFLATED) as zf:
        for path in paths:
            p = Path(path)
            if not p.exists():
                logger.warning(f'⚠️  {p} 不存在，已跳过')
                continue
            if p.is_file():
                zf.write(p, p.name)
            elif p.is_dir():
                for file in p.rglob('*'):
                    if file.is_file():
                        arcname = file.relative_to(p.parent)
                        zf.write(file, arcname)
    size = output_zip.stat().st_size
    logger.info(f'✅ 压缩完成 -> {output_zip}  ({size} bytes)')
    return str(output_zip)


def run_tests_with_threading(test_files=None, num_users=1, send_email=False, email_recipient=None):
    """使用 threading 模块实现多线程执行测试，可选发送邮件"""
    # 确保报告、日志等目录存在
    os.makedirs(REPORTS_DIR, exist_ok=True)
    os.makedirs(REPORTS_TEMP_DIR, exist_ok=True)
    os.makedirs(LOGS_DIR, exist_ok=True)

    # 获取测试文件列表
    if test_files:
        test_files = [os.path.join(TESTS_DIR, f) if not os.path.isabs(f) else f for f in test_files]
    else:
        test_files = []
        for root, dirs, files in os.walk("tests"):
            for file in files:
                if file.startswith("test_") and file.endswith(".py"):
                    test_files.append(os.path.join(root, file))

    # 记录测试开始时间
    start_time = time.time()

    # 定义执行测试的函数
    def run_test_file(file_path, user_id=None):
        # 构建报告目录，支持多用户
        allure_dir = REPORTS_TEMP_DIR
        if user_id is not None:
            allure_dir = os.path.join(REPORTS_TEMP_DIR, f"user_{user_id}")
            os.makedirs(allure_dir, exist_ok=True)

        # 使用 subprocess 调用 pytest 执行单个测试文件
        cmd = [
            'python', '-m', 'pytest',
            file_path,
            '-s',
            '--alluredir', allure_dir,
            '--clean-alluredir'
        ]

        try:
            # 修复编码问题：指定UTF-8编码并添加错误处理
            result = subprocess.run(
                cmd,
                capture_output=True,
                text=True,
                encoding='utf-8',
                errors='replace'  # 替换无法解码的字符
            )
            return result.returncode == 0
        except Exception as e:
            logger.error(f"执行测试文件 {file_path} 时出错: {str(e)}")
            return False

    # 创建并启动线程
    threads = []
    results = []

    for i in range(num_users):
        for test_file in test_files:
            if num_users > 1:
                # 使用带锁的lambda函数确保线程安全
                thread = threading.Thread(
                    target=lambda f=test_file, uid=i:
                    (lambda res: (result_lock.acquire(), results.append(res), result_lock.release()))(
                        run_test_file(f, uid))
                )
            else:
                thread = threading.Thread(
                    target=lambda f=test_file:
                    (lambda res: (result_lock.acquire(), results.append(res), result_lock.release()))(run_test_file(f))
                )
            threads.append(thread)
            thread.start()

    # 等待所有线程完成
    for thread in threads:
        thread.join()

    # 生成最终 Allure 报告
    try:
        # 使用subprocess.run替代os.system，更安全且可捕获输出
        subprocess.run(
            f'allure generate {REPORTS_TEMP_DIR} -o {ALLURE_REPORT_DIR} --clean',
            shell=True,
            check=True,
            encoding='utf-8',
            errors='replace'
        )
    except subprocess.CalledProcessError as e:
        logger.error(f"生成Allure报告失败: {e.stderr}")

    # 计算测试耗时
    duration = time.time() - start_time
    passed_count = sum(results)
    total_count = len(results)

    logger.info(f"测试完成: 通过 {passed_count}/{total_count}, 耗时: {duration:.2f}秒")

    # 如果需要发送邮件
    if send_email and email_recipient:
        try:
            # 打包日志和报告
            zip_path = zip_logs_reports(
                f"test_results_{time.strftime('%Y%m%d_%H%M%S')}.zip",
                LOGS_DIR,
                ALLURE_REPORT_DIR,
                REPORTS_TEMP_DIR
            )

            # 准备邮件内容
            subject = f"测试执行报告 - {time.strftime('%Y-%m-%d %H:%M')}"
            contents = f"""
            测试执行已完成

            详细信息:
            - 测试文件数量: {total_count}
            - 通过数量: {passed_count}
            - 失败数量: {total_count - passed_count}
            - 并发用户数: {num_users}
            - 执行耗时: {duration:.2f}秒
            - 报告目录: {ALLURE_REPORT_DIR}
            - 日志目录: {LOGS_DIR}

            附件包含完整的测试日志和报告。
            """

            # 发送邮件
            send_mail(
                to_mail=email_recipient,
                subject=subject,
                contents=contents,
                attachments=[zip_path]
            )

            logger.info("测试完成并已发送邮件通知")

        except Exception as e:
            logger.error(f"发送邮件过程中发生错误: {e}")

    return {
        "total": total_count,
        "passed": passed_count,
        "failed": total_count - passed_count,
        "duration": duration
    }


def run_single_test_file(file_path, send_email=False, email_recipient=None):
    """运行单个测试文件，可选发送邮件"""
    os.makedirs(REPORTS_DIR, exist_ok=True)
    os.makedirs(REPORTS_TEMP_DIR, exist_ok=True)
    os.makedirs(LOGS_DIR, exist_ok=True)

    # 记录开始时间
    start_time = time.time()

    if not os.path.isabs(file_path):
        file_path = os.path.join(TESTS_DIR, file_path)

    if not os.path.exists(file_path):
        logger.error(f"测试文件不存在: {file_path}")
        return None

    # 执行测试
    try:
        result = subprocess.run([
            'python', '-m', 'pytest',
            file_path,
            '-s',
            '--alluredir', REPORTS_TEMP_DIR,
            '--clean-alluredir'
        ], capture_output=True, text=True, encoding='utf-8', errors='replace')
    except Exception as e:
        logger.error(f"执行测试文件时出错: {str(e)}")
        return None

    # 生成最终 Allure 报告
    try:
        subprocess.run(
            f'allure generate {REPORTS_TEMP_DIR} -o {ALLURE_REPORT_DIR} --clean',
            shell=True,
            check=True,
            encoding='utf-8',
            errors='replace'
        )
    except subprocess.CalledProcessError as e:
        logger.error(f"生成Allure报告失败: {e.stderr}")

    # 计算耗时
    duration = time.time() - start_time
    success = result.returncode == 0

    logger.info(f"单个测试完成: {'通过' if success else '失败'}, 耗时: {duration:.2f}秒")

    # 如果需要发送邮件
    if send_email and email_recipient:
        try:
            zip_path = zip_logs_reports(
                f"single_test_{os.path.basename(file_path)}_{time.strftime('%Y%m%d_%H%M%S')}.zip",
                LOGS_DIR,
                ALLURE_REPORT_DIR,
                REPORTS_TEMP_DIR
            )

            subject = f"单个测试执行完成 - {os.path.basename(file_path)}"
            contents = f"""
            单个测试执行已完成

            测试文件: {file_path}
            执行结果: {'通过' if success else '失败'}
            执行耗时: {duration:.2f}秒
            报告目录: {ALLURE_REPORT_DIR}
            """

            send_mail(
                to_mail=email_recipient,
                subject=subject,
                contents=contents,
                attachments=[zip_path]
            )

        except Exception as e:
            logger.error(f"发送邮件过程中发生错误: {e}")

    return {
        "file": file_path,
        "success": success,
        "duration": duration
    }


# 更新封装的测试执行场景（添加邮件选项）
def run_concurrent_users(num_users=3, test_files=None, send_email=False, email_recipient=None):
    """多个用户并发执行测试，可选发送邮件"""
    logger.info(f"开始执行并发测试，用户数: {num_users}")
    return run_tests_with_threading(test_files, num_users, send_email, email_recipient)


def run_user_service_concurrent(num_users=3, send_email=False, email_recipient=None):
    """多个用户并发执行用户服务测试，可选发送邮件"""
    test_files = ["test_user_service.py"]
    logger.info(f"开始执行用户服务并发测试，用户数: {num_users}")
    return run_tests_with_threading(test_files, num_users, send_email, email_recipient)


def run_payment_concurrent(num_users=3, send_email=False, email_recipient=None):
    """多个用户并发执行支付相关测试，可选发送邮件"""
    test_files = ["test_payment_gateway.py", "test_Dragon_Fruit_Payment.py"]
    logger.info(f"开始执行支付相关并发测试，用户数: {num_users}")
    return run_tests_with_threading(test_files, num_users, send_email, email_recipient)


if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='运行测试')
    parser.add_argument('--file', type=str, help='指定要运行的单个测试文件')
    parser.add_argument('--users', type=int, default=1, help='指定并发用户数')
    parser.add_argument('--email', type=str, help='收件人邮箱地址（如果提供则发送邮件）')
    parser.add_argument('files', nargs='*', help='要运行的测试文件列表')

    args = parser.parse_args()

    send_email = args.email is not None

    try:
        if args.file:
            result = run_single_test_file(args.file, send_email, args.email)
        elif args.files:
            result = run_tests_with_threading(args.files, args.users, send_email, args.email)
        else:
            result = run_tests_with_threading(num_users=args.users, send_email=send_email, email_recipient=args.email)
    except Exception as e:
        logger.error(f"测试执行过程中发生未捕获异常: {str(e)}")
        exit(1)

    # 根据测试结果退出
    exit(0 if result and result.get('failed', 0) == 0 else 1)
